import Logger, { Level } from "@/utils/logger";
import { DirectiveOptions } from "vue";
const logger = new Logger({ prefix: "fake-drag", level: Level.debug });
type VoidFunction = (...args: any[]) => void | Promise<void>;
interface IAnyData {
  [index: string]: any;
}
type status = "default" | "prepare" | "dragging";
interface IState {
  status: status;
  mousedownEventObj: MouseEvent | null;
  dragend: VoidFunction;
  mousemove: VoidFunction;
  meta: { data: IAnyData };
}
const state: IState = {
  status: "default",
  mousedownEventObj: null,
  dragend: () => {},
  mousemove: () => {},
  meta: { data: {} }
};
const mutation = {
  default_prepare: (e: MouseEvent) => {
    logger.log("prepare");
    state.status = "prepare";
    state.mousedownEventObj = e;
  },
  prepare_default: () => {
    logger.log("cancel prepare");
    state.status = "default";
    state.mousedownEventObj = null;
  },
  prepare_dragging: (dragend: VoidFunction, mousemove: VoidFunction) => {
    logger.log("start drag");
    state.status = "dragging";
    state.mousedownEventObj = null;
    state.dragend = dragend || state.dragend;
    state.mousemove = mousemove || state.mousemove;
  },
  dragging_default: () => {
    logger.log("stop drag");
    state.status = "default";
    state.dragend = () => {};
    state.mousemove = () => {};
  },
  setmeta: (data: IAnyData) => {
    state.meta.data = data;
  }
};
const getter = {
  get dragging() {
    return state.status === "dragging";
  }
};
interface IContext {
  handle: {
    selector: string;
    el: HTMLElement;
  };
  sensitivity: { x: number; y: number };
  fn: {
    [index: string]: (e: MouseEvent) => void;
  };
  handler: {
    [index: string]: (e: MouseEvent) => void;
  };
}
interface ExtendedHTMLElement extends HTMLElement {
  __fakedrag: IContext;
}
function isLeftClick(e: MouseEvent) {
  return e.button === 0;
}
function destroy(el: HTMLElement) {
  const ctx = (el as ExtendedHTMLElement).__fakedrag;
  if (ctx !== void 0) {
    if (ctx.handle.el) {
      ctx.handle.el.removeEventListener("mousedown", ctx.handler.mousedown);
      ctx.handle.el.removeEventListener("mouseup", ctx.handler.mouseup);
      ctx.handle.el.removeEventListener("mouseenter", ctx.handler.mouseenter);
      ctx.handle.el.removeEventListener("mouseleave", ctx.handler.mouseleave);
    }
  }
}
function run(fn: VoidFunction | null) {
  if (!fn) return () => {};
  else
    return (e: MouseEvent) => {
      fn({ e, meta: state.meta, set: mutation.setmeta });
    };
}
document.addEventListener("mousemove", e => {
  if (!getter.dragging) return;
  state.mousemove(e);
});
document.addEventListener("mouseup", e => {
  if (state.status === "prepare") mutation.prepare_default();
  if (state.status === "dragging") {
    state.dragend(e);
    mutation.dragging_default();
  }
});
document.addEventListener("drag", e => {
  if (state.status === "prepare") mutation.prepare_default();
  if (state.status === "dragging") {
    e.preventDefault();
    state.dragend(e);
    mutation.dragging_default();
  }
});
export const directive: DirectiveOptions = {
  bind: (el, { value }) => {
    // el.style.userSelect = "none";
    const {
      dragstart,
      dragover,
      dragend,
      dragenter,
      dragleave,
      mousemove,
      sensitivity,
      handle
    } = value || {};
    const ctx: IContext = {
      handle: {
        selector: handle,
        el: handle ? el.querySelector(handle) : el
      },
      sensitivity,
      fn: {
        dragstart: run(dragstart),
        dragover: run(dragover),
        dragend: run(dragend),
        dragenter: run(dragenter),
        dragleave: run(dragleave),
        mousemove: run(mousemove)
      },
      handler: {
        mousedown: (e: MouseEvent) => {
          if (!isLeftClick(e)) {
            if (state.status === "prepare") mutation.prepare_default();
            if (state.status === "dragging") {
              state.dragend(e);
              mutation.dragging_default();
            }
            return;
          } else if (state.status === "default" || state.status === "prepare")
            mutation.default_prepare(e);
        },
        mousemove: (e: MouseEvent) => {
          if (state.status === "prepare" && state.mousedownEventObj) {
            let sx = 10,
              sy = 10;
            const s = state.mousedownEventObj;
            const x = Math.abs(e.clientX - s.clientX);
            const y = Math.abs(e.clientY - s.clientY);
            if (ctx.sensitivity !== void 0) {
              if (typeof ctx.sensitivity === "object") {
                sx = ctx.sensitivity.x || sx;
                sy = ctx.sensitivity.y || sy;
              }
              if (typeof ctx.sensitivity === "number") {
                sx = ctx.sensitivity;
                sy = ctx.sensitivity;
              }
            }
            if (x < sx && y < sy) return;
            ctx.fn.dragstart(state.mousedownEventObj);
            mutation.prepare_dragging(ctx.fn.dragend, ctx.fn.mousemove);
          }
          if (state.status === "dragging") ctx.fn.dragover(e);
        },
        mouseenter: (e: MouseEvent) => {
          if (!getter.dragging) return;
          ctx.fn.dragenter(e);
          logger.log("dragenter");
        },
        mouseleave: (e: MouseEvent) => {
          if (!getter.dragging) return;
          ctx.fn.dragleave(e);
          logger.log("dragleave");
        }
      }
    };
    (el as ExtendedHTMLElement).__fakedrag = ctx;
    if (ctx.handle.el) {
      ctx.handle.el.addEventListener("mousedown", ctx.handler.mousedown);
      ctx.handle.el.addEventListener("mousemove", ctx.handler.mousemove);
      ctx.handle.el.addEventListener("mouseenter", ctx.handler.mouseenter);
      ctx.handle.el.addEventListener("mouseleave", ctx.handler.mouseleave);
    }
  },
  unbind: el => {
    destroy(el);
  }
};
